import os, sys
import ctypes, struct
import cv2  # opencv-python
from time import sleep

if os.name == 'nt': os.system("")  # Hack allow windows native terminal using escape symbol

def getConsoleSize() -> tuple:
    WIN_STDOUT = -11
    fd = ctypes.windll.kernel32.GetStdHandle(WIN_STDOUT)
    cstruct = ctypes.create_string_buffer(22)
    rc_struct = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(fd, cstruct)
    sizey, sizex = 0, 0
    if rc_struct:
        (bufx, bufy, curx, cury, wattr,
        left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", cstruct)
        sizex = right - left + 1
        sizey = bottom - top + 1
    return sizey, sizex

def maximumConsole() -> None:
    SW_MAXIMISE = 3
    hWnd = ctypes.windll.user32.GetForegroundWindow()
    ctypes.windll.user32.ShowWindow(hWnd, SW_MAXIMISE)
    sleep(1)

class PixelAnim(object):
    def __init__(self, fname: str):
        (
            self.cony,
            self.conx,
        ) = getConsoleSize()
        self.buf = ""
        self.buflist = []
        #print(self.cony, self.conx)

        self.video = cv2.VideoCapture(fname)
        self.is_opened = self.video.isOpened()

        self.fps = self.video.get(cv2.CAP_PROP_FPS)
        self.totalfps = self.video.get(cv2.CAP_PROP_FRAME_COUNT)
        self.width, self.height = self.video.get(cv2.CAP_PROP_FRAME_WIDTH), self.video.get(cv2.CAP_PROP_FRAME_HEIGHT)
        #cv2.imshow("title", img)
        #cv2.waitKey(0)

    def __reset_color(self) -> None:
        sys.stderr.write("\x1b[0m")
    
    def __moveto(self, y: int, x: int) -> None:
        sys.stderr.write("\x1b[%d;%dH" % (y, x))

    def __ret_rgb_space(self, r: int, g: int, b: int) -> str:
        if r > 255 or g > 255 or b > 255: raise SyntaxError("r,g,b is int value and should not bigger than 255")
        return "\x1b[48;2;%d;%d;%dm  " % (r, g, b)

    def __set_buf(self, frame: cv2.Mat) -> None:  # 设置缓冲区一次性输出所有字符，加快输出速度
        frame = cv2.resize(frame, (int((self.conx-1)/2), self.cony-1))
        # print(self.cony, self.conx)
        # print(frame.shape)
        self.buf = ""
        for i in range(frame.shape[0]):
            # self.buf += "\x1b[%d,%dH" %(i, 0)
            for j in range(frame.shape[1]):
                b, g, r = frame[i, j]
                self.buf += self.__ret_rgb_space(r, g, b)
            self.buf += '\n'
        self.buf += "\x1b[0m"

    def __hide_cursor(self): sys.stdout.write("\x1b[?25l")
    def __show_cursor(self): sys.stdout.write("\x1b[?25h")

    def play(self) -> None:
        ####sys.stdout.write("\x1bc")
        ####flag, frame = self.video.read()
        ####self.__set_buf(frame)
        ####sys.stdout.write(self.buf)
        self.__hide_cursor()
        sys.stderr.write("\x1bc")
        count = 0
        while self.is_opened:
            flag, frame = self.video.read() 
            if flag:
                sys.stderr.write("准备中...... [%d/%d]" %(count, self.totalfps))
                sys.stderr.flush()
                self.__moveto(0, 0)
                self.__set_buf(frame)
                self.buflist.append(self.buf)
                count += 1
            else: break 
        for i in self.buflist:
            self.__moveto(0, 0)
            sys.stderr.write(i)
            sys.stderr.flush()
            sleep(1/self.fps)
        self.__show_cursor()
        self.__reset_color()
            


if __name__ == '__main__':
    # maximumConsole()
    anim = PixelAnim("video.mp4")
    anim.play()